package com.uxsino.simo.indicator.retractor;

import com.uxsino.commons.utils.config.PropElement;
import com.uxsino.simo.indicator.Indicator;
import com.uxsino.simo.networkentity.EntityInfo;
import com.uxsino.simo.query.QueryContext;
import com.uxsino.simo.query.QueryTemplate;
import com.uxsino.simo.utils.ConfigLoadingContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;

/**
example input:
OK - VM info
2017-03-25 12:56:25.005
Runtime information:
  vmName: Java HotSpot(TM) 64-Bit Server VM
  vmVersion: 25.65-b01

System properties:
  awt.toolkit: sun.awt.windows.WToolkit
  line.separator: 

  os.arch: amd64  
  sun.boot.class.path: C:\Program Files\Java\jdk1.8.0_65\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\rt.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\sunrsasign.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_65\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_65\jre\classes
 
Memory Pool [PS Survivor Space]:
  isValid: true
  mbean.getMemoryManagerNames: 
    PS MarkSweep
    PS Scavenge

Startup arguments:
  -Djava.util.logging.config.file=E:\apache-tomcat-9.0.0.M18\conf\logging.properties
  -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
  -Djdk.tls.ephemeralDHKeySize=2048

usage: first layer and second layer keyname is separated by "###"
<retract indicator="tomcat789_os_info" parser="tomcat_vm_info">
    <field name="os_name" key_name="System properties###os.name" parser="regex" pattern=""/>
 */
public class TomcatVmInfoRetractor extends CompoundValueRetractor {
    private static Logger logger = LoggerFactory.getLogger(TomcatVmInfoRetractor.class);

    private Map<String, String> fieldKeyNameMap = new HashMap<>(fieldRetrievers.size());

    @Override
    public Object doRetract(EntityInfo entity, QueryContext ctxt, QueryTemplate qt, Object obj) {
        if (obj == null) {
            logger.error("retract input is null");
            return null;
        }
        Map<String, Object> result = new HashMap<>(fieldRetrievers.size());
        Map<String, Map<String, String>> infoMap = extractInfo(obj);
        // printInfoMap(infoMap);
        for (Map.Entry<String, IValueRetractor> entry : fieldRetrievers.entrySet()) {
            String keyName = fieldKeyNameMap.get(entry.getKey());
            Object value = null;
            if (null == keyName) {
                logger.error("key_name should not be null for {} ", entry.getKey());
                continue;
            }
            String[] keyNameArr = keyName.split("###");
            if (keyNameArr.length == 2 && infoMap.containsKey(keyNameArr[0])) {
                Map<String, String> infoInnerMap = infoMap.get(keyNameArr[0]);
                if (infoInnerMap.containsKey(keyNameArr[1])) {
                    value = entry.getValue().retract(entity, ctxt, qt, infoInnerMap.get(keyNameArr[1]));
                } else {
                    logger.error("cannot get info of {}, inner keyname does not exist", keyNameArr[1]);
                }
            } else {
                logger.error("cannot get info of {}, outer keyname does not exist", entry.getKey());
            }
            result.put(entry.getKey(), value);
        }
        return result;
    }

    private Map<String, Map<String, String>> extractInfo(Object obj) {
        Map<String, Map<String, String>> resMap = new HashMap<>();
        String objStr = (String) obj;
        String[] strArr = objStr.split("((\\n\\r)|(\\r\\n)){2}|(\\r){2}|(\\n){2}");
        String preOuterKey = null;
        for (int i = 0; i < strArr.length; i++) {
            Map<String, String> innerMap = new HashMap<>();
            String[] lines = strArr[i].split("((\\n\\r)|(\\r\\n)){1}|(\\r){1}|(\\n){1}");
            int index = 0;
            if (i == 0) {
                index = 2;
            }
            String firstLine = lines[index];
            String curOuterKey = "";
            if (firstLine.charAt(firstLine.length() - 1) == ':') {
                curOuterKey = firstLine.substring(0, firstLine.length() - 1);
                index++;
                resMap.put(curOuterKey, innerMap);
            } else {
                curOuterKey = preOuterKey;
                innerMap = resMap.get(curOuterKey);
            }
            StringBuilder sb = new StringBuilder();
            while (index < lines.length) {
                sb.setLength(0);
                sb.append(lines[index++].trim());
                while (index < lines.length && !lines[index].startsWith("  ")) {
                    if (lines[index].startsWith("\t")) {
                        sb = new StringBuilder(sb.toString().trim());
                        sb.append(lines[index].trim());
                        sb.append(", ");
                    } else {
                        sb.append(lines[index]);
                    }
                    index++;
                }
                if (sb.length() >= 2 && sb.charAt(sb.length() - 1) == ' ' && sb.charAt(sb.length() - 2) == ',') {
                    sb.setLength(sb.length() - 2);
                }
                int colIndex = sb.indexOf(":");
                int eqIndex = sb.indexOf("=");
                if (eqIndex != -1 && (colIndex == -1 || eqIndex < colIndex)) {
                    innerMap.put(sb.substring(0, eqIndex), sb.substring(eqIndex + 1).trim());
                } else {
                    innerMap.put(sb.substring(0, colIndex), sb.substring(colIndex + 1).trim());
                }
            }

            preOuterKey = curOuterKey;
        }
        return resMap;
    }

    @SuppressWarnings("unused")
    private void printInfoMap(Map<String, Map<String, String>> map) {
        for (String outKey : map.keySet()) {
            logger.info(outKey + "++++++++++++++++++");
            Map<String, String> inMap = map.get(outKey);
            for (String inKey : inMap.keySet()) {
                logger.info(inKey + "::::" + inMap.get(inKey));
            }
        }
    }

    public void addFieldKeyName(String fieldName, String keyName) {
        fieldKeyNameMap.put(fieldName, keyName);
    }

    @Override
    public void loadProp(Indicator ind, PropElement eRetractor, ConfigLoadingContext lctxt) {
        super.loadProp(ind, eRetractor, lctxt);
        for (PropElement eFld : eRetractor.getElements("field")) {
            String fieldName = eFld.getProp("name", "");
            String keyName = eFld.getProp("key_name", "");

            if (keyName.isEmpty()) {
                keyName = fieldName;
            }
            addFieldKeyName(fieldName, keyName);
        }
    }
}
